<html lang="en">
    <head>
        <title>Ammo.js softbody volume demo 2</title>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
        <style>
            body {
                color: #61443e;
                font-family:Monospace;
                font-size:13px;
                text-align:center;

                background-color: #bfd1e5;
                margin: 0px;
                overflow: hidden;
            }

            #info {
                position: absolute;
                top: 0px; width: 100%;
                padding: 5px;
            }

            a {

                color: #a06851;
            }

        </style>
    </head>
    <body>
		<div id="info">Ammo.js soft body volume demo 2<br>Click to throw a ball</div>
        <div id="container"><br /><br /><br /><br /><br />Loading...</div>

		<script src="../../builds/ammo.js"></script>
        <script src="../js/three/three.min.js"></script>
        <script src="../js/three/OrbitControls.js"></script>
        <script src="../js/three/Detector.js"></script>
        <script src="../js/three/stats.min.js"></script>

        <script>

		Ammo().then(function(Ammo) {

			// Detects webgl
            if ( ! Detector.webgl ) {
                Detector.addGetWebGLMessage();
                document.getElementById( 'container' ).innerHTML = "";
            }

            // - Global variables -

			// Graphics variables
            var container, stats;
            var camera, controls, scene, renderer;
            var textureLoader;
            var clock = new THREE.Clock();
            var clickRequest = false;
            var mouseCoords = new THREE.Vector2();
            var raycaster = new THREE.Raycaster();
            var ballMaterial = new THREE.MeshPhongMaterial( { color: 0x202020 } );
            var pos = new THREE.Vector3();
            var quat = new THREE.Quaternion();

            // Physics variables
            var gravityConstant = -9.8;
			var collisionConfiguration;
			var dispatcher;
			var broadphase;
			var solver;
			var physicsWorld;
			var rigidBodies = [];
			var softBodies = [];
			var margin = 0.05;
			var transformAux1 = new Ammo.btTransform();
			var softBodyHelpers = new Ammo.btSoftBodyHelpers();

			// - Main code -

            init();
            animate();


            // - Functions -

            function init() {

				initGraphics();

				initPhysics();

				createObjects();

				initInput();

            }

            function initGraphics() {

				container = document.getElementById( 'container' );

                camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.2, 2000 );

                scene = new THREE.Scene();

				camera.position.x = -7;
				camera.position.y = 5;
                camera.position.z =  8;

                controls = new THREE.OrbitControls( camera );
                controls.target.y = 2;

                renderer = new THREE.WebGLRenderer();
				renderer.setClearColor( 0xbfd1e5 );
                renderer.setPixelRatio( window.devicePixelRatio );
                renderer.setSize( window.innerWidth, window.innerHeight );
                renderer.shadowMap.enabled = true;

                textureLoader = new THREE.TextureLoader();

				var ambientLight = new THREE.AmbientLight( 0x404040 );
				scene.add( ambientLight );

                var light = new THREE.DirectionalLight( 0xffffff, 1 );
                light.position.set( -10, 10, 5 );
				light.castShadow = true;
				var d = 20;
			    			    light.shadow.camera.left = -d;
			    light.shadow.camera.right = d;
			    light.shadow.camera.top = d;
			    light.shadow.camera.bottom = -d;

			    light.shadow.camera.near = 2;
			    light.shadow.camera.far = 50;

			    light.shadow.mapSize.x = 1024;
			    light.shadow.mapSize.y = 1024;

                scene.add( light );


                container.innerHTML = "";

                container.appendChild( renderer.domElement );

                stats = new Stats();
                stats.domElement.style.position = 'absolute';
                stats.domElement.style.top = '0px';
                container.appendChild( stats.domElement );

                //

                window.addEventListener( 'resize', onWindowResize, false );

            }

			function initPhysics() {

				// Physics configuration

				collisionConfiguration = new Ammo.btSoftBodyRigidBodyCollisionConfiguration();
				dispatcher = new Ammo.btCollisionDispatcher( collisionConfiguration );
				broadphase = new Ammo.btDbvtBroadphase();
				solver = new Ammo.btSequentialImpulseConstraintSolver();
				softBodySolver = new Ammo.btDefaultSoftBodySolver();
				physicsWorld = new Ammo.btSoftRigidDynamicsWorld( dispatcher, broadphase, solver, collisionConfiguration, softBodySolver);
				physicsWorld.setGravity( new Ammo.btVector3( 0, gravityConstant, 0 ) );
				physicsWorld.getWorldInfo().set_m_gravity( new Ammo.btVector3( 0, gravityConstant, 0 ) );

            }

            function createObjects() {

				// Ground
				pos.set( 0, - 0.5, 0 );
				quat.set( 0, 0, 0, 1 );
				var ground = createParalellepiped( 40, 1, 40, 0, pos, quat, new THREE.MeshPhongMaterial( { color: 0xFFFFFF } ) );
				ground.castShadow = true;
				ground.receiveShadow = true;
				textureLoader.load( "../textures/grid.png", function( texture ) {
					texture.wrapS = THREE.RepeatWrapping;
					texture.wrapT = THREE.RepeatWrapping;
					texture.repeat.set( 40, 40 );
					ground.material.map = texture;
					ground.material.needsUpdate = true;
				} );


                // Create soft volumes
                var volumeMass = 15;

                var bx = 2;
                var by = 1;
                var bz = 3;
                var nn = 5;
                createSoftBox( bx, by, bz, nn * bx, nn * by, nn * bz, 0, 5, 0, volumeMass, 0 );


                // Some obstacle
                pos.set( 0, 0.5, 0 );
                quat.set( 0, 0, 0, 1 );
                var obstacle = createParalellepiped( 6, 1, 1, 0, pos, quat, new THREE.MeshPhongMaterial( { color: 0x606060 } ) );
                obstacle.castShadow = true;
                obstacle.receiveShadow = true;


            }

            function createSoftBox( sizeX, sizeY, sizeZ, numPointsX, numPointsY, numPointsZ, tX, tY, tZ, mass, pressure ) {

                if ( numPointsX < 2 || numPointsY < 2 || numPointsZ < 2 ) {
                    return;
                }

                // Offset is the numbers assigned to 8 vertices of the cube in ascending Z, Y, X in this order.
                // indexFromOffset is the vertex index increase for a given offset.
                var indexFromOffset = [];
                for ( var offset = 0; offset < 8; offset++ ) {
                    var a = offset & 1 ? 1 : 0;
                    var b = offset & 2 ? 1 : 0;
                    var c = offset & 4 ? 1 : 0;
                    var index = a + b * numPointsX + c * numPointsX * numPointsY;
                    indexFromOffset[ offset ] = index;
                }

                // Construct BufferGeometry

                var numVertices = numPointsX * numPointsY * numPointsZ;
                var numFaces = 4 * ( ( numPointsX - 1 ) * ( numPointsY - 1 ) + ( numPointsX - 1 ) * ( numPointsZ - 1 ) + ( numPointsY - 1 ) * ( numPointsZ - 1 ) );

                var bufferGeom = new THREE.BufferGeometry();
                var vertices = new Float32Array( numVertices * 3 );
                var normals = new Float32Array( numVertices * 3 );
                var indices = new ( numFaces * 3 > 65535 ? Uint32Array : Uint16Array )( numFaces * 3 );

                // Create vertices and faces
                var sx = sizeX / ( numPointsX - 1 );
                var sy = sizeY / ( numPointsY - 1 );
                var sz = sizeZ / ( numPointsZ - 1 );
                var numFacesAdded = 0;
                for ( var p = 0, k = 0; k < numPointsZ; k++ ) {
                    for ( var j = 0; j < numPointsY; j++ ) {
                        for ( var i = 0; i < numPointsX; i++ ) {

                            // Vertex and normal
                            var p3 = p * 3;
                            vertices[ p3 ] = i * sx - sizeX * 0.5;
                            normals[ p3++ ] = 0;
                            vertices[ p3 ] = j * sy - sizeY * 0.5;
                            normals[ p3++ ] = 0;
                            vertices[ p3 ] = k * sz - sizeZ * 0.5;
                            normals[ p3 ] = 0;

                            // XY faces
                            if ( k == 0 && i < numPointsX - 1 && j < numPointsY - 1 ) {

                                var faceIndex = numFacesAdded * 3;

                                indices[ faceIndex++ ] = p + indexFromOffset[ 0 ];
                                indices[ faceIndex++ ] = p + indexFromOffset[ 3 ];
                                indices[ faceIndex++ ] = p + indexFromOffset[ 1 ];

                                indices[ faceIndex++ ] = p + indexFromOffset[ 0 ];
                                indices[ faceIndex++ ] = p + indexFromOffset[ 2 ];
                                indices[ faceIndex++ ] = p + indexFromOffset[ 3 ];

                                numFacesAdded += 2;
                            }
                            if ( k == numPointsZ - 2 && i < numPointsX - 1 && j < numPointsY - 1 ) {

                                var faceIndex = numFacesAdded * 3;

                                indices[ faceIndex++ ] = p + indexFromOffset[ 7 ];
                                indices[ faceIndex++ ] = p + indexFromOffset[ 6 ];
                                indices[ faceIndex++ ] = p + indexFromOffset[ 5 ];

                                indices[ faceIndex++ ] = p + indexFromOffset[ 5 ];
                                indices[ faceIndex++ ] = p + indexFromOffset[ 6 ];
                                indices[ faceIndex++ ] = p + indexFromOffset[ 4 ];

                                numFacesAdded += 2;
                            }

                            // XZ faces
                            if ( j == 0 && i < numPointsX - 1 && k < numPointsZ - 1 ) {

                                var faceIndex = numFacesAdded * 3;

                                indices[ faceIndex++ ] = p + indexFromOffset[ 0 ];
                                indices[ faceIndex++ ] = p + indexFromOffset[ 5 ];
                                indices[ faceIndex++ ] = p + indexFromOffset[ 4 ];

                                indices[ faceIndex++ ] = p + indexFromOffset[ 0 ];
                                indices[ faceIndex++ ] = p + indexFromOffset[ 1 ];
                                indices[ faceIndex++ ] = p + indexFromOffset[ 5 ];

                                numFacesAdded += 2;
                            }
                            if ( j == numPointsY - 2 && i < numPointsX - 1 && k < numPointsZ - 1 ) {

                                var faceIndex = numFacesAdded * 3;

                                indices[ faceIndex++ ] = p + indexFromOffset[ 3 ];
                                indices[ faceIndex++ ] = p + indexFromOffset[ 2 ];
                                indices[ faceIndex++ ] = p + indexFromOffset[ 6 ];

                                indices[ faceIndex++ ] = p + indexFromOffset[ 3 ];
                                indices[ faceIndex++ ] = p + indexFromOffset[ 6 ];
                                indices[ faceIndex++ ] = p + indexFromOffset[ 7 ];

                                numFacesAdded += 2;
                            }

                            // YZ faces
                            if ( i == 0 && j < numPointsY - 1 && k < numPointsZ - 1 ) {

                                var faceIndex = numFacesAdded * 3;

                                indices[ faceIndex++ ] = p + indexFromOffset[ 0 ];
                                indices[ faceIndex++ ] = p + indexFromOffset[ 6 ];
                                indices[ faceIndex++ ] = p + indexFromOffset[ 2 ];

                                indices[ faceIndex++ ] = p + indexFromOffset[ 0 ];
                                indices[ faceIndex++ ] = p + indexFromOffset[ 4 ];
                                indices[ faceIndex++ ] = p + indexFromOffset[ 6 ];

                                numFacesAdded += 2;
                            }
                            if ( i == numPointsX - 2 && j < numPointsY - 1 && k < numPointsZ - 1 ) {

                                var faceIndex = numFacesAdded * 3;

                                indices[ faceIndex++ ] = p + indexFromOffset[ 1 ];
                                indices[ faceIndex++ ] = p + indexFromOffset[ 3 ];
                                indices[ faceIndex++ ] = p + indexFromOffset[ 5 ];

                                indices[ faceIndex++ ] = p + indexFromOffset[ 3 ];
                                indices[ faceIndex++ ] = p + indexFromOffset[ 7 ];
                                indices[ faceIndex++ ] = p + indexFromOffset[ 5 ];

                                numFacesAdded += 2;
                            }

                            p++;
                        }
                    }
                }

                bufferGeom.setIndex( new THREE.BufferAttribute( indices, 1 ) );
                bufferGeom.addAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) );
                bufferGeom.addAttribute( 'normal', new THREE.BufferAttribute( normals, 3 ) );

                bufferGeom.translate( tX, tY, tZ );

                // Create mesh from geometry
                var volume = new THREE.Mesh( bufferGeom, new THREE.MeshPhongMaterial( { color: 0xFFFFFF, wireframe: true } ) );
                volume.castShadow = true;
                volume.receiveShadow = true;
                volume.frustumCulled = false;
                scene.add( volume );

                // Create soft body
                var vectorTemp = new Ammo.btVector3( 0, 0, 0 );
                vectorTemp.setValue( vertices[ 0 ], vertices[ 1 ], vertices[ 2 ] );

                var volumeSoftBody = new Ammo.btSoftBody( physicsWorld.getWorldInfo(), 1, vectorTemp, [ 1.0 ] );

                var physMat0 = volumeSoftBody.get_m_materials().at( 0 );

                for ( var i = 1, il = vertices.length / 3; i < il; i++ ) {
                    var i3 = i * 3;
                    vectorTemp.setValue( vertices[ i3 ], vertices[ i3 + 1 ], vertices[ i3 + 2 ] );
                    volumeSoftBody.appendNode( vectorTemp, 1.0 );
                }

                for ( var i = 0, il = indices.length / 3; i < il; i++ ) {
                    var i3 = i * 3;
                    volumeSoftBody.appendFace( indices[ i3 ], indices[ i3 + 1 ], indices[ i3 + 2 ] );
                }

                // Create tetrahedrons
                var p = 0;

                function newTetra( i0, i1, i2, i3, i4 ) {

                    var v0 = p + indexFromOffset[ i0 ];
                    var v1 = p + indexFromOffset[ i1 ];
                    var v2 = p + indexFromOffset[ i2 ];
                    var v3 = p + indexFromOffset[ i3 ];
                    var v4 = p + indexFromOffset[ i4 ];

                    volumeSoftBody.appendTetra( v0, v1, v2, v3, v4 );

                    volumeSoftBody.appendLink( v0, v1, physMat0, true );
                    volumeSoftBody.appendLink( v0, v2, physMat0, true );
                    volumeSoftBody.appendLink( v0, v3, physMat0, true );
                    volumeSoftBody.appendLink( v1, v2, physMat0, true );
                    volumeSoftBody.appendLink( v2, v3, physMat0, true );
                    volumeSoftBody.appendLink( v3, v1, physMat0, true );

                }

                for ( var k = 0; k < numPointsZ; k++ ) {
                    for ( var j = 0; j < numPointsY; j++ ) {
                        for ( var i = 0; i < numPointsX; i++ ) {

                            if ( i < numPointsX - 1 && j < numPointsY - 1 && k < numPointsZ - 1 ) {

                                // Creates 5 tetrahedrons for each cube
                                newTetra( 0, 4, 5, 6 );
                                newTetra( 0, 2, 3, 6 );
                                newTetra( 0, 1, 3, 5 );
                                newTetra( 3, 5, 6, 7 );
                                newTetra( 0, 3, 5, 6 );
                                /*
                                volumeSoftBody.appendTetra( p + indexFromOffset[ 0 ], p + indexFromOffset[ 4 ], p + indexFromOffset[ 5 ], p + indexFromOffset[ 6 ] );
                                volumeSoftBody.appendTetra( p + indexFromOffset[ 0 ], p + indexFromOffset[ 2 ], p + indexFromOffset[ 3 ], p + indexFromOffset[ 6 ] );
                                volumeSoftBody.appendTetra( p + indexFromOffset[ 0 ], p + indexFromOffset[ 1 ], p + indexFromOffset[ 3 ], p + indexFromOffset[ 5 ] );
                                volumeSoftBody.appendTetra( p + indexFromOffset[ 3 ], p + indexFromOffset[ 5 ], p + indexFromOffset[ 6 ], p + indexFromOffset[ 7 ] );
                                volumeSoftBody.appendTetra( p + indexFromOffset[ 0 ], p + indexFromOffset[ 3 ], p + indexFromOffset[ 5 ], p + indexFromOffset[ 6 ] );
                                */
                            }

                            p++;
                        }
                    }
                }

                // Config soft body

                var sbConfig = volumeSoftBody.get_m_cfg();
                sbConfig.set_viterations( 40 );
                sbConfig.set_piterations( 40 );

                // Soft-soft and soft-rigid collisions
                sbConfig.set_collisions( 0x11 );

                // Friction
                sbConfig.set_kDF( 0.1 );
                // Damping
                sbConfig.set_kDP( 0.01 );
                // Pressure
                sbConfig.set_kPR( pressure );
                // Stiffness
                var stiffness = 0.05;
                physMat0.set_m_kLST( stiffness );
                physMat0.set_m_kAST( stiffness );
                physMat0.set_m_kVST( stiffness );

                volumeSoftBody.setTotalMass( mass, false )
                Ammo.castObject( volumeSoftBody, Ammo.btCollisionObject ).getCollisionShape().setMargin( margin );
                physicsWorld.addSoftBody( volumeSoftBody, 1, -1 );
                volume.userData.physicsBody = volumeSoftBody;
                // Disable deactivation
                volumeSoftBody.setActivationState( 4 );

                softBodies.push( volume );

            }

            function createParalellepiped( sx, sy, sz, mass, pos, quat, material ) {

				var threeObject = new THREE.Mesh( new THREE.BoxGeometry( sx, sy, sz, 1, 1, 1 ), material );
				var shape = new Ammo.btBoxShape( new Ammo.btVector3( sx * 0.5, sy * 0.5, sz * 0.5 ) );
				shape.setMargin( margin );

				createRigidBody( threeObject, shape, mass, pos, quat );

				return threeObject;

            }

            function createRigidBody( threeObject, physicsShape, mass, pos, quat ) {

            	threeObject.position.copy( pos );
            	threeObject.quaternion.copy( quat );

				var transform = new Ammo.btTransform();
    			transform.setIdentity();
    			transform.setOrigin( new Ammo.btVector3( pos.x, pos.y, pos.z ) );
    			transform.setRotation( new Ammo.btQuaternion( quat.x, quat.y, quat.z, quat.w ) );
				var motionState = new Ammo.btDefaultMotionState( transform );

				var localInertia = new Ammo.btVector3( 0, 0, 0 );
		    	physicsShape.calculateLocalInertia( mass, localInertia );

		    	var rbInfo = new Ammo.btRigidBodyConstructionInfo( mass, motionState, physicsShape, localInertia );
		    	var body = new Ammo.btRigidBody( rbInfo );

				threeObject.userData.physicsBody = body;

				scene.add( threeObject );

				if ( mass > 0 ) {
					rigidBodies.push( threeObject );

					// Disable deactivation
					body.setActivationState( 4 );
				}

				physicsWorld.addRigidBody( body );

                return body;
            }

            function initInput() {

                window.addEventListener( 'mousedown', function( event ) {

                    if ( ! clickRequest ) {

                        mouseCoords.set(
                            ( event.clientX / window.innerWidth ) * 2 - 1,
                            - ( event.clientY / window.innerHeight ) * 2 + 1
                        );

                        clickRequest = true;

                    }

                }, false );

            }

            function processClick() {

                if ( clickRequest ) {

                    raycaster.setFromCamera( mouseCoords, camera );

                    // Creates a ball
                    var ballMass = 0.7;
                    var ballRadius = 0.4;

                    var ball = new THREE.Mesh( new THREE.SphereGeometry( ballRadius, 18, 16 ), ballMaterial );
                    ball.castShadow = true;
                    ball.receiveShadow = true;
                    var ballShape = new Ammo.btSphereShape( ballRadius );
                    ballShape.setMargin( margin );
                    pos.copy( raycaster.ray.direction );
                    pos.add( raycaster.ray.origin );
                    quat.set( 0, 0, 0, 1 );
                    var ballBody = createRigidBody( ball, ballShape, ballMass, pos, quat );
                    ballBody.setFriction( 0.5 );

                    pos.copy( raycaster.ray.direction );
                    pos.multiplyScalar( 14 );
                    ballBody.setLinearVelocity( new Ammo.btVector3( pos.x, pos.y, pos.z ) );

                    clickRequest = false;

                }

            }

            function onWindowResize() {

                camera.aspect = window.innerWidth / window.innerHeight;
                camera.updateProjectionMatrix();

                renderer.setSize( window.innerWidth, window.innerHeight );

            }

            function animate() {

                requestAnimationFrame( animate );

                render();
                stats.update();

            }

            function render() {

            	var deltaTime = clock.getDelta();

            	updatePhysics( deltaTime );

            	processClick();

                controls.update( deltaTime );

                renderer.render( scene, camera );

            }

            function updatePhysics( deltaTime ) {

                // Step world
                physicsWorld.stepSimulation( deltaTime, 10 );

                // Update soft volumes
                for ( var i = 0, il = softBodies.length; i < il; i++ ) {
                    var volume = softBodies[ i ];
                    var geometry = volume.geometry;
                    var softBody = volume.userData.physicsBody;
                    var volumePositions = geometry.attributes.position.array;
                    var volumeNormals = geometry.attributes.normal.array;
                    var numVerts = volumePositions.length / 3;
                    var nodes = softBody.get_m_nodes();
                    var p = 0;
                    for ( var j = 0; j < numVerts; j ++ ) {
                        var node = nodes.at( j );
                        var nodePos = node.get_m_x();
                        var nodeNormal = node.get_m_n();

                        volumePositions[ p ] = nodePos.x();
                        volumeNormals[ p++ ] = nodeNormal.x();
                        volumePositions[ p ] = nodePos.y();
                        volumeNormals[ p++ ] = nodeNormal.y();
                        volumePositions[ p ] = nodePos.z();
                        volumeNormals[ p++ ] = nodeNormal.z();

                    }

                    geometry.attributes.position.needsUpdate = true;
                    geometry.attributes.normal.needsUpdate = true;

                }

			    // Update rigid bodies
			    for ( var i = 0, il = rigidBodies.length; i < il; i++ ) {
			    	var objThree = rigidBodies[ i ];
			    	var objPhys = objThree.userData.physicsBody;
					var ms = objPhys.getMotionState();
					if ( ms ) {

			        	ms.getWorldTransform( transformAux1 );
						var p = transformAux1.getOrigin();
						var q = transformAux1.getRotation();
						objThree.position.set( p.x(), p.y(), p.z() );
						objThree.quaternion.set( q.x(), q.y(), q.z(), q.w() );

			      	}
			    }

			}

		});

        </script>

    </body>
</html>
